BUILD:=./build
SRC:=.
$(BUILD)/boot/%.o: $(SRC)/boot/%.asm  # 表示使用模式规则，所有.asm文件都使用该规则， $<表示输入文件， $@ 表示输出文件
	$(shell mkdir -p $(dir $@)) # 创建文件夹
	nasm -f bin $< -o $@

##########################
CFLAGS:= -m32 # 表示32位程序
CFLAGS+= -fno-builtin # 不需要gcc的内置函数，比如memcpy函数就是内置函数，遇到这个函数gcc会替换成自己的内置函数，因为性能更高。但在这里无法依赖任何库
CFLAGS+= -nostdinc # 不需要标准头文件，也就是c的标准库无法使用
CFLAGS+= -fno-pic # pic是position independent code的缩写，这里表示不需要位置无关的代码。应用是位置无关的，可加载到内存任何位置执行，但内核中很难使用起来，只适合位置相关
CFLAGS+= -fno-pie # 不需要位置无关的可执行程序，position independent executable
CFLAGS+= -nostdlib # 不需要标准库，所有的标准库都需要自己写
CFLAGS+= -fno-stack-protector # 不需要栈保护，这样可以很容易的分析栈
CFLAGS:= $(strip ${CFLAGS}) # strip 函数的作用是去除字符串中开头和结尾的空白字符（包括空格、制表符等），并且会将字符串中连续的多个空白字符压缩为单个空格。

DEBUG:= -g # 表示增加调试信息
INCLUDE:= -I$(SRC)/include # 指定头文件目录

$(BUILD)/kernel/%.o: $(SRC)/kernel/%.c  # 编译kernel.c，使用gcc命令
	$(shell mkdir -p $(dir $@)) # 创建文件夹
	gcc $(CFLAGS) $(DEBUG) $(INCLUDE) -c $< -o $@ # 这里编译kernel文件夹下的汇编，这里输出格式是elf32格式，gcc生成的可执行程序就是elf文件

##########################
# 这里增加$(DEBUG)选项，也可以增加一些调试信息
$(BUILD)/kernel/%.o: $(SRC)/kernel/%.asm  # 表示使用模式规则，所有.asm文件都使用该规则， $<表示输入文件， $@ 表示输出文件
	$(shell mkdir -p $(dir $@)) # 创建文件夹
	nasm -f elf32 $(DEBUG) $< -o $@ # 这里编译kernel文件夹下的汇编，这里输出格式是elf32格式，gcc生成的可执行程序就是elf文件
# 这里编译出可重定向elf32文件后，需要链接成内核文件，链接则需要制定入口地址
ENTRY_POINT:=0x10000 # 这里设定放在可用区间内的0x10000位置，其他位置也可以
# 所以需要一个kernel.bin，依赖于start.o
# ld命令解释：ld链接命令，格式是elf_i386，-static表示静态链接，$^表示所有依赖文件，以便有多个文件时扩展。
# 生成kernel.bin还需依赖kernel.o
$(BUILD)/bin/kernel.bin: $(BUILD)/kernel/start.o \
	$(BUILD)/kernel/kernel.o
	$(shell mkdir -p $(dir $@))
	ld -m elf_i386 -static $^ -o $@ -Ttext $(ENTRY_POINT)
# 之后再进行细微处理，将ELF格式的kernel.bin文件转换为纯二进制文件system.bin‌，
# 剥离所有 ELF 格式的元数据（如文件头、段信息、符号表等），仅保留可执行的代码和数据部分，
# 便于在加载内存后可以直接运行，主要进行了去除元数据‌、合并段、填充地址间隙等操作
$(BUILD)/bin/system.bin: $(BUILD)/bin/kernel.bin
	objcopy -O binary $< $@
# 之后则是获取符号表，便于后续调试用
$(BUILD)/bin/system.map: $(BUILD)/bin/kernel.bin
	nm $< | sort > $@

# 之后将生成的system.bin一起编入启动镜像中，其中system.map不是必须的
$(BUILD)/bin/boot.img: $(BUILD)/boot/boot.o \
          $(BUILD)/boot/loader.o \
		  $(BUILD)/bin/system.bin \
		  $(BUILD)/bin/system.map
	$(shell mkdir -p $(dir $@))
    # yes的作用是重复执行make boot.img时会弹出文件已存在，是否覆盖，会直接输入y信号
	yes | bximage -q -hd=16 -func=create -sectsize=512 -imgmode=flat $@ # $@表示目标文件
	dd if=$(BUILD)/boot/boot.o of=$@ bs=512 count=1 conv=notrunc
	dd if=$(BUILD)/boot/loader.o of=$@ bs=512 count=4 seek=2 conv=notrunc  # 写入第二个扇区开始的4个扇区共2k
	dd if=$(BUILD)/bin/system.bin of=$@ bs=512 count=200 seek=10 conv=notrunc  # 写入硬盘的第10个扇区开始的200个扇区共200k，预估应足够本次OS内核使用

.PHONY: clean # PHON依赖，表示，每一次clean都要执行
clean:
	rm -rf *.o
	rm -rf *.bin
	rm -rf *.img
	rm -rf *.img.lock
	rm -rf bochsout.txt
	rm -rf bx_enh_dbg.ini
	rm -rf $(BUILD)

.PHONY: bochs
bochs: $(BUILD)/bin/boot.img
	bochs -q

test: $(BUILD)/boot/boot.o # 用于测试文件夹是否正确创建和产生正确的输出
test2: $(BUILD)/bin/boot.img
test3: $(BUILD)/kernel/start.o
test4: $(BUILD)/bin/kernel.bin

all:
	make clean
	make $(BUILD)/bin/boot.img
	make bochs

.PHONY: qemu
qemu: $(BUILD)/bin/boot.img  # -m配置内存，默认128M，-boot配置启动盘，还可选a b软盘或d cd-ROM，-hda表示硬盘的文件，$<表示就是输入的boot.img
	qemu-system-i386 -m 32M -boot c -hda $<

.PHONY: qemu-debug
qemu-debug: $(BUILD)/bin/boot.img  # -s 监听1234端口，-S 表示启动后就挂起，等待gdb连接
	qemu-system-i386 -s -S -m 32M -boot c -hda $<

$(BUILD)/bin/boot.vmdk: $(BUILD)/bin/boot.img # -p显示进展
	qemu-img convert -pO vmdk $< $@

.PHONY: vmdk
vmdk: $(BUILD)/bin/boot.vmdk

